/* eds_types.c - SR type extensions for EDB
 * cezanne@clunix,com, 2005.2
 *
 * Copyright (c) 2005 by Clunix Inc. All Rights Reserved.
 */

#include "config.h"

#include "eds.h"
#include "edb.h"
#include "edl.h"

srt_t	ST_CLUSTER;
srt_t	ST_NODE;
srt_t	ST_MID;
srt_t	ST_MMID;
srt_t	ST_FLAGS;
srt_t	ST_CTX_NAME;
srt_t	ST_CTXCLS_NAME;
srt_t	ST_STAGE;
srt_t	ST_EMBID;

static const char *flags[] = {
	"solid", NULL
};

static ECM_INLINE ecm_size_t
sizeof_escaped_token(char *token, char *seps)
{
	ecm_size_t	size = 0;
	char		*p;

	p = token;
	while (*p) {
		if (ecm_strchr(seps, *p) || *p == '\\')
			size++;
		size++;
		p++;
	}
	return size;
}

static char *
print_token(char *token, char *seps)
{
	char	*p,  *q;
	char	*escaped;

	if (token == NULL)
		return NULL;
	p = token;
	escaped = q = ecm_malloc(sizeof_escaped_token(token, seps) + 1);
	while (*p) {
		if (ecm_strchr(seps, *p) || *p == '\\')
			*q++ = '\\';
		*q++ = *p;
		p++;
	}
	*q = '\0';
	return escaped;
}

static char *
print_edexpr(sr_t *sr_edexpr)
{
	char	*edexpr, *temp;

	edexpr = edl_sr_to_edexpr(sr_edexpr);
	temp = print_token(edexpr, "]");
	ecm_free(edexpr);
	ecm_asprintf(&edexpr, "[%s]", temp);
	ecm_free(temp);
	return edexpr;
}

static char *
print_LNP(sr_t *sr_LNP)
{
	if (sr_get_type(sr_LNP) == ST_EDEXPR)
		return print_edexpr(sr_LNP);
	else
		return string_LNP(sr_LNP);
}

static char *
print_CTXs(sr_t *sr_CTXs)
{
	sr_t	*sr_CTX;
	char	*CTXs = NULL, *temp;

	SR_FOREACH_CHILD(sr_CTXs, NULL, sr_CTX) {
		if (CTXs) {
			ecm_asprintf(&temp, "%s,%s", CTXs, sr_get_str(sr_CTX));
			ecm_free(CTXs);
			CTXs = temp;
		}
		else
			CTXs = ecm_strdup(sr_get_str(sr_CTX));
	}
	return CTXs;
}

static char *
print_ARG(sr_t *sr_ARG)
{
	if (sr_get_type(sr_ARG) == ST_EDEXPR)
		return print_edexpr(sr_ARG);
	else {
		char	*asr;
		sr_asr_asprint(&asr, sr_ARG);
		return asr;
	}
}

static char *
get_step_str(sr_t *sr_step)
{
	sr_t	*sr_LNP, *sr_CTX, *sr_ARG;
	char	*LNP = NULL, *CTX = NULL, *ARG = NULL;
	char	*step = NULL;

	sr_LNP = sr_getc(sr_step, "LNP");
	if (sr_LNP) {
		char	*temp;
		LNP = print_LNP(sr_LNP);
		temp = print_token(LNP, ":");
		ecm_free(LNP);
		LNP = temp;
	}

	sr_CTX = sr_getc(sr_step, "CTX");
	if (sr_get_num_child(sr_CTX) > 0)
		CTX = print_CTXs(sr_CTX);

	sr_ARG = sr_getc(sr_step, "ARG");
	if (sr_ARG)
		ARG = print_ARG(sr_ARG);

	if (ecm_empty_str(ARG)) {
		if (ecm_empty_str(CTX))
			step = ecm_strdup(LNP);
		else {
			ecm_asprintf(&step, "%s:%s",
				     ecm_safe_str(LNP),
				     ecm_safe_str(CTX));
		}
	}
	else {
		ecm_asprintf(&step, "%s:%s:%s",
			     ecm_safe_str(LNP),
			     ecm_safe_str(CTX),
			     ecm_safe_str(ARG));
	}

	ecm_free(LNP);
	ecm_free(CTX);
	ecm_free(ARG);

	return step;
}

static int
srt_stage_print(srt_t srt, char *buf, ecm_size_t *pbuflen, sr_val_t srv)
{
	sr_t	*sr_stage, *sr_step;
	ecm_size_t	asrvlen;
	char	*asrv = NULL, *step, *temp;

	sr_stage = srv.dir;

	SR_FOREACH_CHILD(sr_stage, NULL, sr_step) {
		step = get_step_str(sr_step);
		if (step == NULL) {
			ecm_free(asrv);
			return 0;
		}
		temp = print_token(step, ";");
		ecm_free(step);
		step = temp;
		if (asrv) {
			ecm_asprintf(&temp, "%s;%s", asrv, step);
			ecm_free(asrv);
			ecm_free(step);
			asrv = temp;
		}
		else
			asrv = step;
	}

	asrvlen = ecm_strlen(asrv);
	if (*pbuflen < asrvlen) {
		ecm_free(asrv);
		*pbuflen = asrvlen;
		return -1;
	}
	ecm_strcpy(buf, asrv);
	ecm_free(asrv);
	return asrvlen;
}

static ECMBOOL
get_token(char **pstr, char **ptoken, char tok)
{
	char	*p, *q;

	if (*pstr == NULL) {
		*ptoken = NULL;
		return ECM_TRUE;
	}
	p = *pstr;
	q = *ptoken = NULL;
	while (*p) {
		if (*p == tok) {
			if (q)
				*q = '\0';
			*pstr = p + 1;
			return ECM_TRUE;
		}
		switch (*p) {
		case '\\':
			if (p[1] == tok)
				p++;
			else {
				if (*ptoken == NULL)
					q = *ptoken = p;
				*q++ = *p++;
			}
			/* fall through */
		default:
			if (*ptoken == NULL)
				q = *ptoken = p;
			*q++ = *p;
			break;
		}
		p++;
	}
	if (q)
		*q = '\0';
	*pstr = NULL;
	return ECM_TRUE;
}

static char *
get_edexpr(char *str)
{
	char	*ret = NULL, *p, *q;

	ret = p = q = str;
	while (*p) {
		switch (*p) {
		case ']':
			*q = '\0';
			return ret;
		case '\\':
			if (p[1] == ']') {
				*q = p[1];
				p++;
			}
			else
				*q = *p;
			break;
		default:
			*q = *p;
			break;
		}
		p++; q++;
	}
	return NULL;
}

static sr_t *
get_sr_edexpr(char *str)
{
	char	*edexpr;

	if (*str != '[')
		return NULL;
	edexpr = get_edexpr(str + 1);
	return edl_get_edexpr(edexpr, NULL, NULL);
}

static ECMBOOL
parse_LNP(sr_t	*sr_step, char *LNP)
{
	sr_t	*sr_step_LNP;
	sr_t	*sr_edexpr;

	if (ecm_empty_str(LNP))
		return ECM_TRUE;

	sr_edexpr = get_sr_edexpr(LNP);
	if (sr_edexpr)
		sr_step_LNP = sr_edexpr;
	else {
		sr_t	*sr_LNP;
		sr_LNP = get_sr_LNP(LNP);
		if (sr_LNP == NULL)
			return ECM_FALSE;
		sr_step_LNP = sr_LNP;
	}
	sr_append(sr_step, sr_set_name(sr_step_LNP, "LNP"));
	return ECM_TRUE;
}

static void
parse_CTX(sr_t *sr_step, char *CTXs)
{
	ecm_strlist_t	ctxlist;
	char		*CTX;

	ctxlist = ecm_strlist_create(ECM_FALSE);
	ecm_strlist_add_multiple(ctxlist, CTXs, ',');
	if (ecm_strlist_count(ctxlist) > 0) {
		sr_t	*sr_CTXs;

		sr_CTXs = sr_addc(sr_step, "CTX", sr_create_dir());
		for (CTX = ecm_strlist_first(ctxlist);
		     CTX; CTX = ecm_strlist_next(ctxlist))
			sr_append(sr_CTXs, sr_create_str(ST_CTX_NAME, ecm_trim(CTX)));
	}
	ecm_strlist_free(ctxlist);
}

static ECMBOOL
parse_ARG(sr_t *sr_step, char *ARG)
{
	sr_t	*sr_edexpr, *sr_step_ARG;

	if (ecm_empty_str(ARG))
		return ECM_TRUE;

	sr_edexpr = get_sr_edexpr(ARG);
	if (sr_edexpr)
		sr_step_ARG = sr_edexpr;
	else
		sr_step_ARG = sr_create_str(ST_STR, ARG);

	sr_append(sr_step, sr_set_name(sr_step_ARG, "ARG"));
	return ECM_TRUE;
}

static sr_t *
parse_stage(char *stage)
{
	sr_t	*sr_step;
	char	*LNP, *CTX;

	if (!get_token(&stage, &LNP, ':'))
		return NULL;

	sr_step = sr_create_dir();
	if (!parse_LNP(sr_step, LNP)) {
		sr_free(sr_step);
		return NULL;
	}

	if (!get_token(&stage, &CTX, ':')) {
		sr_free(sr_step);
		return NULL;
	}
	parse_CTX(sr_step, CTX);

	if (!parse_ARG(sr_step, stage)) {
		sr_free(sr_step);
		return NULL;
	}

	return sr_step;
}

static ECMBOOL
srt_stage_scan(srt_t srt, char *asrv, sr_val_t *psrv)
{
	char	*stage;
	sr_t	*sr_stage, *sr_step;

	sr_stage = sr_create(ST_STAGE);

	while (ECM_TRUE) {
		if (ecm_empty_str(asrv))
			break;
		if (!get_token(&asrv, &stage, ';')) {
			sr_free(sr_stage);
			return ECM_FALSE;
		}
		if ((sr_step = parse_stage(stage)) == NULL) {
			sr_free(sr_stage);
			return ECM_FALSE;
		}
		sr_append(sr_stage, sr_step);
	}
	*psrv = sr_val_dir(sr_stage);
	return ECM_TRUE;
}

static sr_parser_t	stage_parser = {
	srt_stage_print, srt_stage_scan
};

void
eds_build_anchor_stage(sr_t *sr_stage, sr_t *sr_LNP)
{
	sr_t	*sr_step;

	sr_step = sr_create_dir();
	sr_addc(sr_step, "LNP", sr_LNP);
	sr_add_first(sr_stage, sr_step);
}

ECMBOOL
eds_initialize_type(void)
{
	static ECMBOOL	initialized = ECM_FALSE;

	if (initialized)
		return ECM_TRUE;

	ST_CLUSTER = sr_type_add(SR_BT_DIR, "cluster", NULL, NULL);
	ST_NODE = sr_type_add(SR_BT_DIR, "node", NULL, NULL);
	ST_MID = sr_type_add(SR_BT_DIR, "mid", NULL, NULL);
	ST_MMID = sr_type_add(SR_BT_DIR, "mmid", NULL, NULL);
	ST_FLAGS = sr_enumtype_add("flags", flags, ECM_TRUE, ST_NULL, ST_NULL);
	ST_CTX_NAME = sr_type_add(SR_BT_STR, "ctxname", NULL, NULL);
	ST_CTXCLS_NAME = sr_type_add(SR_BT_STR, "ctxclsname", NULL, NULL);
	ST_STAGE = sr_type_add(SR_BT_DIR, "stage", NULL, &stage_parser);
	ST_EMBID = sr_type_add(SR_BT_STR, "embid", NULL, NULL);
	return ECM_TRUE;
}

static ECM_INLINE void
set_sr_parser(const char *srtstr, void *handle, sr_parser_t *pparser)
{
	char		*symname;
	sr_printer_t	printer = NULL;
	sr_scanner_t	scanner = NULL;

	if (handle) {
		ecm_asprintf(&symname, "%s_printer", srtstr);
		printer = (sr_printer_t)ecm_dlsym(handle, symname);
		ecm_free(symname);

		ecm_asprintf(&symname, "%s_scanner", srtstr);
		scanner = (sr_scanner_t)ecm_dlsym(handle, symname);
		ecm_free(symname);
	}

	pparser->print = printer;
	pparser->scan = scanner;
}

static ECM_INLINE srt_t
add_EPE_type_enumbitwise(const char *srtstr, srt_t srt_base, srt_t srt, srt_t srt_super, sr_t *sr_body)
{
	sr_t		*sr_strs, *sr_str;
	ECMBOOL		bitwise;
	const char	**enumstrs = NULL;
	int		i, num_child;

	bitwise = sr_get_bool(sr_geti(sr_body, 0));
	sr_strs = sr_geti(sr_body, 1);
	num_child = sr_get_num_child(sr_strs);
	if (num_child > 0) {
		enumstrs = (const char **)ecm_malloc(sizeof(char *) * (num_child + 1));
		enumstrs[num_child] = NULL;
	}

	i = 0;
	SR_FOREACH_CHILD(sr_strs, NULL, sr_str)
		enumstrs[i++] = ecm_strdup(sr_get_str(sr_str));
	srt = sr_enumtype_add(srtstr, enumstrs, bitwise, srt, srt_super);
	if (srt == ST_NULL) {
		i = 0;
		SR_FOREACH_CHILD(sr_strs, NULL, sr_str)
			ecm_free(enumstrs[i++]);
		ecm_free(enumstrs);
		return ST_NULL;
	}
	return srt;
}

static srt_t
get_base_type(sr_t *sr_supers)
{
	srt_t	srt_bt = ST_NULL;
	sr_t	*sr_super;

	SR_FOREACH_CHILD(sr_supers, NULL, sr_super) {
		srt_t	srt_super;
		if (!search_srt(sr_get_str(sr_super), &srt_super))
			return ST_NULL;
		if (srt_bt == ST_NULL)
			srt_bt = srt_get_base_type(srt_super);
		else if (srt_bt != srt_get_base_type(srt_super))
			return ST_NULL;
	}
	if (srt_bt == ST_NULL)
		return SR_BT_DIR;
	return srt_bt;
}

static srt_t
get_super_enum_srt(sr_t *sr_supers)
{
	srt_t	srt_super;

	if (sr_get_num_child(sr_supers) != 1)
		return ST_NULL;
	if (!search_srt(sr_geti_str(sr_supers, 0), &srt_super) ||
	    !srt_is_enum(srt_super))
		return ST_NULL;
	return srt_super;
}

static srt_t
add_PE_type(const char *srtstr, sr_t *sr_supers, srt_t srt, sr_t *sr_extra, void *handle)
{
	sr_parser_t	parser;
	srt_t		srt_enum_super = ST_NULL;

	if (sr_extra || 
	    (srt_enum_super = get_super_enum_srt(sr_supers)) != ST_NULL)
		return add_EPE_type_enumbitwise(srtstr, SR_BT_INT, srt, srt_enum_super, sr_extra);
	set_sr_parser(srtstr, handle, &parser);
	return sr_type_add(srt, srtstr, sr_supers, &parser);
}

static ECM_INLINE srt_t
add_DE_type(const char *srtstr, sr_t *sr_supers, srt_t srt, void *handle)
{
	sr_parser_t	parser;

	set_sr_parser(srtstr, handle, &parser);
	return sr_dirtype_add(srtstr, sr_supers, &parser, srt);
}

eds_err_t
eds_type_add(sr_t *sr_type_info, void *handle)
{
	const char	*srtstr;
	srt_t	srt, srt_bt;
	sr_t	*sr_supers;
	sr_t	*sr_extra;

	srtstr = sr_getc_str(sr_type_info, "name");
	sr_supers = sr_getc(sr_type_info, "supers");
	srt = sr_getc_int(sr_type_info, "srt");
	sr_extra = sr_getc(sr_type_info, "extra");

	if ((srt_bt = get_base_type(sr_supers)) == ST_NULL)
		return -ERR_EDS_SUPER_SRT;
	if (srt_bt == ST_DIR)
		srt = add_DE_type(srtstr, sr_supers, srt, handle);
	else {
		if (srt == ST_NULL)
			srt = srt_bt;
		srt = add_PE_type(srtstr, sr_supers, srt, sr_extra, handle);
	}

	if (srt == ST_NULL)
		return -ERR_EDS_GENERAL;
	if (sr_getc(sr_type_info, "srt") == NULL)
		sr_createc_int(sr_type_info, "srt", ST_INT, srt);
	return EDS_OK;
}

void
eds_type_remove(sr_t *sr_type_info)
{
	srt_t	srt;

	srt = sr_getc_int(sr_type_info, "srt");
	sr_type_remove(srt);
}

ECMBOOL
eds_types_add(sr_t *sr_type_infos, void *handle)
{
	sr_t	*sr_type_info;

	if (sr_type_infos == NULL)
		return ECM_FALSE;
	SR_FOREACH_CHILD(sr_type_infos, NULL, sr_type_info)
		if (eds_type_add(sr_type_info, handle) != EDS_OK)
			return ECM_FALSE;
	return ECM_TRUE;
}

void
eds_types_remove(sr_t *sr_smod_descs)
{
	sr_t	*sr_smod_desc;

	SR_FOREACH_CHILD(sr_smod_descs, NULL, sr_smod_desc)
		eds_type_remove(sr_smod_desc);
}

sr_t *
load_sobj(const char *clxdir, const char *modname)
{       
	sr_t    *sr_ret = NULL;
	int     fd;
	char    *buf, *sobj_name;
	ecm_size_t	filesize;

	ecm_asprintf(&sobj_name, "%s/modules/%s.sobj", clxdir, modname);

	if ((fd = ecm_open(sobj_name, ECM_O_RDONLY)) < 0) {
		return NULL;
	}
	if (!ecm_filesize(fd, &filesize)) {
		ecm_close(fd);
		return NULL;
	}
	buf = (char *)ecm_malloc(sizeof(char) * filesize);
	if (ecm_read(fd, buf, &filesize))
		sr_decode(buf, &filesize, &sr_ret);
	ecm_free(buf);
	ecm_close(fd);
        ecm_free(sobj_name);
	return sr_ret;
}
